import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Compiles a collection of up to 5 object files into a single executable load file.
 * <p>
 * Additionally, this program outputs a unique user report per run. Given an
 * arbitrary loadFile name "loadFile.xe", user report name would be of the form
 * "loadFile-LinkingReport#.txt", where # is a unique index that prevents multiple
 * reports generated from the same load file from overwriting each other.
 * This is done for ease of grading.
 * 
 * @author Tobin Chee
 *
 * Change Log:<br>
 * 	- 05/27/09 - Tobin Chee<br>
 * 		- Added inner type ObjectFile to simplify parameter passing.<br><br>
 * 	- 05/29/09 - Tobin Chee<br>
 * 		- Declared multiple global constants to eliminate the use of magic numbers.
 */
public class LinkerLoader {
	/**
	 * Version History:<br>
	 * 	- 	0: In the interest of saving memory, the original parsing structure was setup
	 * 		so that no large data tables were stored and output directly to file.<br><br>
	 * 
	 * 	- 	1: Parsing structure updated to store all data in an intermediate table
	 * 		so that more robust error logging and reporting could be done.
	 */
	private static final int VERSION_NUMBER = 1;
	/**
	 * Revision History:<br>
	 * 	- 	0: No changes since version 1
	 */
	private static final int REVISION_NUMBER = 2;
	/** Maximum number of arguments allowed from the command line.
	 */
	private static final int MAX_NUM_OF_ARGS = 6;
	/** Minimum number of arguments allowed from the command line. 
	 */
	private static final int MIN_NUM_OF_ARGS = 2;
	/** The minimum load address allowed.
	 */
	private static final int MIN_LOAD_ADDRESS = 0;
	/** The maximum load address allowed.
	 */
	private static final int MAX_LOAD_ADDRESS = 4095;
	/** All numbers in current object file format are in hex. 
	 */
	private static final int OBJECT_FILE_BASE = 16;
	/** length of the linking record.
	 */
	private static final int LEN_LINKING_RECORD = 4;
	/** Number of sets of memory references.
	 */
	private static final int NUM_MEM_REF_TUPLES = 4;
	/** Length of memory reference sets.
	 */
	private static final int LEN_MEM_REF_TUPLES = 4;
	/** Number of fields allowed to begin memory reference sets
	 */
	private static final int NUM_PRE_MEM_REF_FIELDS = 3;
	/**
	 * Number of hex digits in the instruction code.
	 */
	private static final int LEN_INSTRUCTION_CODE = 8;
	/**
	 * Number of hex digits in all unsigned hex fields, such as execution start address.
	 */
	private static final int LEN_UNSIGNED_NUM_FIELDS = 4;
	/**
	 * Number of hex digits in the S fields of the memory reference tuples.<br><br>
	 * 
	 * Note: The length of S fields used to represent symbol labels are not explicitly
	 * limited. However, any S field longer than 24 characters will result in a 
	 * symbol not defined error.
	 */
	private static final int LEN_S_FIELDS = 8;
	/**
	 * Instruction code for a NOP. Used to replace invalid instructions.
	 */
	private static final int NOP = 0x0047;
	/**
	 * Maximum valid length of a symbol label.
	 */
	private static final int LEN_SYMBOL_LABEL = 24;
	
	/**
	 * Inner class used to simplify parameter passing.
	 * 
	 * @author Tobin Chee
	 *
	 */
	static class ObjectFile{
		BufferedReader inputStream;
		String moduleName;
		int executionStartAddr;
		int moduleLength;
		int assemblerVersion;
		int assemblerRevision;
		int loadAddress;
		int numLinkingRecords;
		int numTextRecords;
		int relocationAdjustment;
		
		public ObjectFile(){
		}
	}
	
	/**
	 * Inner exception type used to track fatal errors.
	 * 
	 * @author Tobin Chee
	 */
	static class FatalObjectFileFormatException extends Exception {
		int errorCode;
		FatalObjectFileFormatException(){
			super();
		}
		FatalObjectFileFormatException(String message){
			super(message);
		}
		FatalObjectFileFormatException(int errCode){
			errorCode = errCode;
		}
	}
	
	/**
	 * Inner exception type used to track non-fatal errors.
	 * 
	 * @author Tobin Chee
	 */
	static class NonFatalObjectFileFormatException extends Exception {
		int errorCode;
		NonFatalObjectFileFormatException(){
			super();
		}
		NonFatalObjectFileFormatException(String message){
			super(message);
		}
		NonFatalObjectFileFormatException(int errCode){
			errorCode = errCode;
		}
	}
	
	/**
	 * See the documentation for {@link LinkerLoader} for a description of
	 * the tasks this method performs.
	 * <p>
	 * Validates command line arguments and calls through to sub-methods.
	 * Catches all fatal errors thrown by sub-methods and reports them to the
	 * console. 
	 * 
	 * <p>
	 * REQUIRES:<br>
	 * - loadfile name (arg[0]) must end in ".xe"<br>
	 * - object file names (args[1-5]) must end in ".o"
	 * 
	 * @param args file names:<br>
	 * args[0] - name of the load file<br>
	 * args[1] - name of the first object file(main program)<br>
	 * args[2] - name of the second object file<br>
	 * args[4] - name of the third object file<br>
	 * args[5] - name of the fourth object file<br>
	 * args[6] - name of the fifth object file<br>
	 * Any additional args are ignored and a warning issued.
	 * @author Tobin Chee
	 * 
	 * Change Log:<br>
	 * 	- Added declaration intermediateTable to accommodate new parsing structure.
	 * 	- Changed calls to related methods to include intermediateVersion as a parameter.
	 */
	public static void main(String[] args) {
		//Special Case: We are setting this at 0 because we don't actually have an operating system to deal with.
		
		if(validateArgs(args)){ //if args are valid
			try{
				BufferedWriter loadFile, reportFile;
				LinkingSymbolTable symbolTable = new LinkingSymbolTable();
				ObjectFile[] objectFiles;
				ObjectFileTokenizer tokenizer = new ObjectFileTokenizer1();
				String tempReportFileName;
				int reportFileIndex = 0;
				IntermediateTable intermediateVersion = new IntermediateTable();
				ErrorTable errors = new ErrorTable();
				
				//instantiate loadFile
				loadFile = new BufferedWriter(new FileWriter(args[0]));
				
				//Determine the user report file name and instantiate it.
				//Outputs a new user report file each time the program is run for ease of grading.
                tempReportFileName = args[1].substring(0, args[1].length() - 2) + "-LinkingReport";
                
                while(new File(tempReportFileName + reportFileIndex + ".txt").exists()){
                	reportFileIndex++;
                }
                tempReportFileName = tempReportFileName + reportFileIndex + ".txt";
                reportFile = new BufferedWriter(new FileWriter(tempReportFileName));
				
				//instantiate objectFiles
				if(args.length > MAX_NUM_OF_ARGS){
					objectFiles = new ObjectFile[MAX_NUM_OF_ARGS];
				}
				else{
					objectFiles = new ObjectFile[args.length - 1];
				}
				
				for(int i = 0; i < objectFiles.length; i++){
					objectFiles[i] = new ObjectFile();
					objectFiles[i].inputStream = new BufferedReader(new FileReader(args[i + 1]));
				}
				
				try{
					//populates loadAddress, executionStartAddr, moduleLength, moduleName, assemblerVersion and assemblerRevision for each ObjectFile in objectFiles[]
					parseObjectFile(objectFiles, intermediateVersion, symbolTable, tokenizer);
					resolveReferences(intermediateVersion, symbolTable, objectFiles);
					putLoadFile(loadFile, intermediateVersion, objectFiles);
				} catch(NullPointerException npe){
					System.out.println("FATAL ERROR: Unexpected end of object file.");
					System.out.println("See " + tempReportFileName + " for a more detailed error report.");
				} catch(IOException ioe){
					System.out.println("FATAL ERROR: I/O error.");
					System.out.println("See " + tempReportFileName + " for a more detailed error report.");
				} catch (FatalObjectFileFormatException foffe){
					//log error
					intermediateVersion.addError(intermediateVersion.length() - 1, foffe.errorCode);
					
					System.out.println("FATAL ERROR: Invalid object file format");
					System.out.println("See " + tempReportFileName + " for a more detailed error report.");
					
//					//TODO remove debug statements
//					System.out.println(foffe.getMessage());
//					foffe.printStackTrace();
				} catch(Exception e){
					//This should never happen, unless there's a bug. print stack trace
					System.out.println("FATAL ERROR: LinkerLoader programming error.");
					System.out.println("See " + tempReportFileName + " for a more detailed error report.");
					e.printStackTrace();
				}
				putUserReport(reportFile, intermediateVersion, symbolTable, errors);
				reportFile.close();
				loadFile.close();
				
				for(ObjectFile temp: objectFiles){
					temp.inputStream.close();
				}
				System.out.println("LinkerLoader has exited successfully");
			} catch(IOException ioe){
				System.out.println("FATAL ERROR: I/O error.");
			}
		}
		//else do nothing, all errors are logged by validateArgs
		

	}
	
	/**
	 * Writes user report to file.
	 * <p>
	 * User report is a formatted table containing the following for each line of source code in the object files:<br>
	 * - Absolute memory location, instruction code, source line # and the line of source code.<br>
	 * NOTE: source line # does not accurately reflect, the actual line # in each object file. Rather it
	 * represents the line # if all of the object files were strung together into one file.
	 * 
	 * @param reportFile report file to be written to.
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param symbolTable table containing symbols stored from parsing.
	 * @param errors error table containing error codes and descriptions.
	 * @throws IOException if an I/O error occurs.
	 * Change Log:<br>
	 * 	- 06/02/09 - Tobin Chee<br>
	 * 		- Changed method to throw IOException instead of catching it.
	 */
	private static void putUserReport(BufferedWriter reportFile, IntermediateTable intermediateVersion, LinkingSymbolTable symbolTable, ErrorTable errors) throws IOException{
		intermediateVersion.putToFile(reportFile, errors);
		reportFile.write("\n");
		reportFile.write("SYMBOL TABLE:\n");
		symbolTable.putToFile(reportFile);
	}
	
	/**
	 * Attempts resolve the address of all symbols referenced by instructions.
	 * <p>
	 * If all of the symbols referenced by an instruction are found in the symbol table, the address field is adjusted by that
	 * value. If any of the symbols referenced by an instruction are not found in the symbol table, the instruction is
	 * changed to a NOP and an error logged.<br><br>
	 * 
	 * REQUIRES:<br>
	 * - startIndex >=0<br>
	 * - endIndex <= intermediateVersion.length()
	 * 
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param symbolTable table containing symbols stored from parsing.
	 * @param objectFiles array of object files.
	 * @throws Exception if there is a bug in the program. Normally this should not be thrown.
	 * 
	 * 
	 * Change Log:<br>
	 * 	- 05/27/09 - Tobin Chee<br>
	 * 		- Changed method name from "resolveAddresses" to "resolveReferences".<br><br>
	 *	- 06/01/09 - Tobin Chee<br>
	 *		- Added parameters relocationValue, startIndex and endIndex to facilitate relocation of 'R' flagged fields.
	 */
	private static void resolveReferences(IntermediateTable intermediateVersion, LinkingSymbolTable symbolTable, ObjectFile[] objectFiles) throws Exception{
		
		for(int i = 0; i < intermediateVersion.length(); i++){
			
			if(intermediateVersion.getConsumesMemory(i)){
				ArithmeticExpression exp = intermediateVersion.getOperandSymbolExpression(i);
				
				//any expressions with 2 or more terms needs to be solved, the rest are empty
				if(exp.length() > 1){
					int addr;
					
					for(int j = 0; j < exp.length(); j++){
						int value;
						ArithmeticExpression.Sign sign;
						char arc;
						String label;
						
						arc = exp.getARCFlag(j);
						
						if(arc == 'A'){
							//do nothing, absolute terms don't need adjustment
						}
						else if(arc == 'R'){
							//adjust term for relocation
							value = exp.getTermMagnitude(j);
							sign = exp.getTermSign(j);
							
							if(sign == ArithmeticExpression.Sign.NEGATIVE){
								value = -value;
								exp.invertSign(j);
							}
							//else do nothing, already positive
							
							int locationCounter = intermediateVersion.getLocationCounter(i);
							
							for(int k = 0; k < objectFiles.length; k++){
								ObjectFile obj = objectFiles[k];
								
								//if instruction is in a program, adjust by its relocation adjustment
								if((locationCounter <= (obj.loadAddress + obj.moduleLength)) &&
										(locationCounter >= obj.loadAddress)){
									value += obj.relocationAdjustment;
									exp.updateTermValue(j, value);
									//no need to iterate anymore
									break;
								}
								//else, keep iterating
							}
						}
						else if(arc == 'C'){
							//find term in symbolTable
							label = exp.getSymbolLabel(j);
							
							if(symbolTable.contains(label)){
								value = symbolTable.getAddress(symbolTable.indexOf(label));
								
								if(value < 0){
									value = Math.abs(value);
									exp.invertSign(j);
								}
								//else no adjustment necessary, value is already positive
								exp.updateTermValue(j, value);
							}
							else{
								//error, object file contains undefined external references
								intermediateVersion.updateOpCode(i, NOP);
								intermediateVersion.addError(intermediateVersion.length() - 1, 207);
								//don't throw an exception
							}
						}
						else{
							//Program error, this condition should not occur unless there are bugs.
							throw new Exception();
						}
					}
					addr = intermediateVersion.getAddr(i);
					addr += exp.evaluate();
					if((addr >= MIN_LOAD_ADDRESS) && (addr <= MAX_LOAD_ADDRESS)){
						intermediateVersion.updateAddr(i, addr);
					}
					else{
						//error, instruction out of range (0 <= addr <= 4096)
						throw new FatalObjectFileFormatException(247);
					}
				}
				//else, do nothing. empty expression
			}
			//else do nothing
		}
		
	}
	
	/**
	 * Converts a string representing an integer of the given base and length to its 32-bit two's complement integer value.
	 * <p>
	 * REQUIRES: length <= 32
	 * 
	 * @param value string representation of a two's complement integer.
	 * @param base number base of integer represented by value.
	 * @param length number of hex digits in value (0 <= length <= 8).
	 * @throws NumberFormatException if token is not a valid string representation of an
	 * integer of the designated base and length.
	 */
	private static int convertTwosCompToDec(String value, int base, int length) throws NumberFormatException{
		
		//if string rep of two's comp
		if(value.length() <= length){
			return (int)Long.parseLong(value, base);
		}
		else{
			//throw exception so caller can log error appropriately.
			throw new NumberFormatException();
		}
	}
	
	/**
	 * Determines if label is a valid label in the CSE7000 assembly language.
	 * <p>
	 * Label is considered a valid label if label.length <= LEN_LABEL and
	 * contains only alpha-numeric characters.
	 * 
	 * @param label symbol label.
	 * @return true, if label.length <= LEN_LABEL and label contains only alpha-numeric characters; false, otherwise.
	 */
	private static boolean isValidLabel(String label){
		
		if(label.length() <= LEN_SYMBOL_LABEL){
			
			for(int i = 0; i < label.length(); i++){
				
				//if not alpha-numeric char
				if(!Character.isLetterOrDigit(label.charAt(i))){
					return false;
				}
			}
			return true;
		}
		else{ //label too long
			return false;
		}
	
	}
	
	private static void parseHeaderRecord(ObjectFile objectFile, ObjectFileTokenizer tokenizer, StringBuffer sourceLine,
			StringBuffer token, ObjectFileTokenizer.TokenType[] type, int fieldIndex) throws Exception{
		
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);
		
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);

			if(type[0] == ObjectFileTokenizer.TokenType.TEXT){
				
				switch (fieldIndex) {
				case 1:
					//NOTE: error msg is logged with bounds hard coded in, so if LEN_SYMBOL_LABEL changes, so must the error message.
					//module name
					//validate program name
					if(isValidLabel(token.toString())){
						objectFile.moduleName = token.toString();
						break;
					}
					else{
						//fatal error, expecting module name to be a valid alpha-numeric string of length <= 24
						throw new FatalObjectFileFormatException(217);
					}
				case 2:
					//date
					//Do nothing, we don't need this
					break;
				case 3:
					//time
					//Do nothing, we don't need this
					break;
				case 4:
					//program length (in words)
					try{
						int temp = parseNumberField(token.toString(), MIN_LOAD_ADDRESS, MAX_LOAD_ADDRESS + 1, false, OBJECT_FILE_BASE);
						objectFile.moduleLength = temp;
						break;
					} catch(NumberFormatException nfe){
						//NOTE: error msg is logged with bounds hard coded in, so if MIN_LOAD_ADDRESS or MAX_LOAD_ADDRESS changes, so must the error message.
						//fatal error, expecting module length field to contain a hex string in the range (0 <= mod_len <= 4096)
						throw new FatalObjectFileFormatException(216);
					}
				case 5:
					//load address (hex)
					try{
						//NOTE: error msg is logged with bounds hard coded in, so if MIN_LOAD_ADDRESS or MAX_LOAD_ADDRESS changes, so must the error message.
						//fatal error, expecting load address field to be a valid hex integer in the range (0 <= load address <= 4095)
						objectFile.loadAddress = parseNumberField(token.toString(), MIN_LOAD_ADDRESS, MAX_LOAD_ADDRESS, false, OBJECT_FILE_BASE);
						break;
					} catch(NumberFormatException nfe){
						throw new FatalObjectFileFormatException(215);
					}
				case 6:
					//number of linking records (hex)
					try{
						objectFile.numLinkingRecords = parseNumberField(token.toString(), 0, Integer.MAX_VALUE, false, OBJECT_FILE_BASE);
						break;
					} catch(NumberFormatException nfe){
						//fatal error, expecting number of linking records field to contain a 32-bit hex integer
						throw new FatalObjectFileFormatException(214);
					}
				case 7:
					//number of text records (hex)
					try{
						objectFile.numTextRecords = parseNumberField(token.toString(), 0, MAX_LOAD_ADDRESS + 1, false, OBJECT_FILE_BASE);
						break;
					} catch(NumberFormatException nfe){
						//fatal error, expecting number of text records field to contain a 32-bit hex integer
						throw new FatalObjectFileFormatException(213);
					}
				case 8:
					//execution start address (hex)
					try{
						objectFile.executionStartAddr = parseNumberField(token.toString(), objectFile.loadAddress, MAX_LOAD_ADDRESS, false, OBJECT_FILE_BASE);
					}
					catch (NumberFormatException nfe){
						//fatal error, expecting execution start address field to contain a 32-bit hex string
						throw new FatalObjectFileFormatException(212);
					}
					break;
				case 9:
					//"CSE 7000"
					if(token.toString().compareTo("CSE7000") != 0){
						//fatal, programming language name must equal "CSE7000"
						throw new FatalObjectFileFormatException(211);
					}
					//else do nothing, it's valid
					break;
				case 10:
					//assembler version#
					//Do nothing, we don't need this
					//NOTE: we choose not to validate this field because it is unused.
					break;
				case 11:
					//assembler revision#
					//Do nothing, we don't need this
					//NOTE: we choose not to validate this field because it is unused.
					break;
				default:
					//throw exception, this is the result of a fatal programmer error.
					throw new Exception();
				}
				parseHeaderRecord(objectFile, tokenizer, sourceLine, token, type, fieldIndex + 1);
			}
			else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
				//fatal error, invalid header record. unexpected end of line
				throw new FatalObjectFileFormatException(208);
			}
			else //if type = ERROR
			{
				//fatal error, invalid header record. fields may only contain alpha-numeric characters in addition to {'-', '*', '/', ':'}.
				throw new FatalObjectFileFormatException(209);
			}
		}
		else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
			
			if(fieldIndex != 12){
				//error invalid header record. unexpected end of file.
				throw new FatalObjectFileFormatException(208);
			}
			//else, valid end of header record, do nothing
		}
		else
		{
			//error, invalid header record. expecting separator between fields
			throw new FatalObjectFileFormatException(210);
		}
	}
	

	/**
	 * Reads in all the linking records in the object file.
	 * <p>
	 * Data from parsing the linking records is stored in the symbolTable.
	 * 
	 * @param objectFile object file to be read from.
	 * @param symbolTable table containing symbols stored from parsing.
	 * @param tokenizer string tokenizer.
	 * @param sourceLine line of source code.
	 * @param token string token.
	 * @param type type of token.
	 * @param fieldIndex index of field being parsed by the current recursive iteration.
	 * @throws FatalObjectFileFormatException if syntax errors are found in the object files.
	 * @throws Exception if there is a bug in the program. Normally this should not be thrown. 
	 */
	private static void parseLinkingRecord(ObjectFile objectFile, IntermediateTable intermediateVersion, LinkingSymbol symbol, ObjectFileTokenizer tokenizer,
			StringBuffer sourceLine, StringBuffer token, ObjectFileTokenizer.TokenType[] type, int fieldIndex) throws Exception{
		
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);
		
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);

			if(type[0] == ObjectFileTokenizer.TokenType.TEXT){
				
				switch (fieldIndex) {
				case 1:
					//entry name
					symbol.updateLabel(token.toString());
					break;
				case 2:
					//entry addr
					int addr = parseNumberField(token.toString(), MIN_LOAD_ADDRESS, MAX_LOAD_ADDRESS, true, 16);
					//adjust for relocation
					addr += objectFile.relocationAdjustment;
					symbol.updateAddress(addr);
					break;
				case 3:
					//module name
					if(token.toString().compareTo(objectFile.moduleName) == 0){
						symbol.updateModuleName(token.toString());
					}
					else{ //if linking module name doesn't match header module name

						//error, linking module name does not match header module name
						throw new FatalObjectFileFormatException(240);
					}
					break;
				default:
					//error too many fields in Linking record
					throw new FatalObjectFileFormatException(241);
				}
				parseLinkingRecord(objectFile, intermediateVersion, symbol, tokenizer, sourceLine, token, type, fieldIndex + 1);
			}
			else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
				//error, unexpected end of line
				throw new FatalObjectFileFormatException(243);
			}
			else //if type = ERROR
			{
				//error, expecting alph-numeric string
				throw new FatalObjectFileFormatException(242);
			}
		}
		else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
			
			if(fieldIndex < LEN_LINKING_RECORD){
				//error, unexpected end of line
				throw new FatalObjectFileFormatException(243);
			}
			//else, valid end of linking record, do nothing
		}
		else
		{
			//error, expecting separator between fields
			throw new FatalObjectFileFormatException(244);
		}
	}
	
	/**
	 * Reads in a group of four fields representing {ARC, sign, arithmetic operator, reference} within the text record.
	 * <p>
	 * Data from parsing these fields is stored in intermediateVersion.
	 * @param objectFile an object file.
	 * @param exp arithmetic expression.
	 * @param tokenizer string tokenizer.
	 * @param sourceLine line of source code.
	 * @param token string token.
	 * @param type type of token.
	 * @param isBlank true, if a blank field is found; false, otherwise.
	 * @param fieldIndex index of field being parsed by current recursive iteration.
	 * @throws NonFatalObjectFileFormatException if syntax errors are found while parsing.
	 * 
	 * Change Log:<br>
	 * 	- 06/02/09 - Tobin Chee<br>
	 * 		- Updated throw statements with error numbers from the error table.
	 */
	private static void parseAddrExpressionTuple(ObjectFile objectFile, IntermediateTable intermediateVersion, ArithmeticExpression exp,
			ObjectFileTokenizer tokenizer, StringBuffer sourceLine, StringBuffer token, ObjectFileTokenizer.TokenType[] type,
			boolean isBlank, int fieldIndex) throws Exception{
		
		ArithmeticExpression.Sign sign;
		ArithmeticExpression.ArithmeticOperator operator;
		char arc;
		int magnitude;
		
		//ARC flag:
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);

		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
				if(!isBlank){
					isBlank = true;
				}
				sourceLine.insert(0, token.toString());
				arc = 'x'; //arbitrary value to appease compiler
			}
			else if (type[0] == ObjectFileTokenizer.TokenType.TEXT){
				if((token.toString().compareTo("A") == 0) ||
						(token.toString().compareTo("R") == 0) ||
						(token.toString().compareTo("C") == 0)){
					arc = token.charAt(0);
				}
				else{
					//error, ARC field is not a valid A, R, C value
					throw new NonFatalObjectFileFormatException(206);
				}
			}
			else{
				//error, expecting an A, R, C value, or an empty operand
				throw new NonFatalObjectFileFormatException(233);
			}
		}
		else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
			//error, unexpected end of line
			throw new NonFatalObjectFileFormatException(237);
		}
		else{
			//error, expecting separator between fields
			throw new NonFatalObjectFileFormatException(234);
		}
		fieldIndex++;
		
		//arithmetic operator:
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);
		
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
				if(isBlank){
					sourceLine.insert(0, token.toString());
				}
				else{
					//error, can't have a blank after a filled field
					throw new NonFatalObjectFileFormatException(235);
				}
				operator = ArithmeticExpression.ArithmeticOperator.ADD; //arbitrary default value to appease compiler
			}
			else if (type[0] == ObjectFileTokenizer.TokenType.TEXT){
				try{
				operator = ArithmeticExpression.stringToArithmeticOperator(token.toString());
				} catch(Exception e){
					//TODO catching Exception can potentially hide a fatal programming error
					
					//error, expecting arithmetic operator
					throw new NonFatalObjectFileFormatException(232);
				}
			}
			else{
				if(isBlank){
					//error, if one field is empty, all fields must be empty
					throw new NonFatalObjectFileFormatException(236);
				}
				else{
					//error, expecting an arithmetic operator
					throw new NonFatalObjectFileFormatException(232);
				}
			}
		}
		else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
			//error, unexpected end of line
			throw new NonFatalObjectFileFormatException(237);
		}
		else{ //if text or token
			//error, expecting separator between fields
			throw new NonFatalObjectFileFormatException(234);
		}
		fieldIndex++;
		
		//sign:
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);
		
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
				if(isBlank){
					sourceLine.insert(0, token.toString());
				}
				else{
					//error, can't have a blank after a filled field
					throw new NonFatalObjectFileFormatException(235);
				}
				sign = ArithmeticExpression.Sign.NEGATIVE; //arbitrary default value to appease compiler
			}
			else if (type[0] == ObjectFileTokenizer.TokenType.TEXT){
				try{
					sign = ArithmeticExpression.stringToSign(token.toString());
				} catch(Exception e){
					//TODO catching Exception can potentially hide a fatal programming error
					
					//error, expecting number sign
					throw new NonFatalObjectFileFormatException(231);
				}
			}
			else{
				if(isBlank){
					//error, if one field is empty, all fields must be empty
					throw new NonFatalObjectFileFormatException(236);
				}
				else{
					//error, expecting a sign "+" or "-"
					throw new NonFatalObjectFileFormatException(231);
				}
			}
		}
		else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
			//error, unexpected end of line
			throw new NonFatalObjectFileFormatException(237);
		}
		else{
			//error, expecting separator between fields
			throw new NonFatalObjectFileFormatException(234);
		}
		fieldIndex++;
		
		//memory reference:
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);
		
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
				
				if(isBlank){
					sourceLine.insert(0, token.toString());
				}
				else{
					//error, can't have a blank after a filled field
					throw new NonFatalObjectFileFormatException(235);
				}
			}
			else if (type[0] == ObjectFileTokenizer.TokenType.TEXT){

				if(!isBlank){
					if((arc == 'A') || (arc == 'R')){ //if A or R, treat field as a hex string
						try{
							magnitude = convertTwosCompToDec(token.toString(), OBJECT_FILE_BASE, LEN_S_FIELDS);
							
							if(arc == 'R'){
								//adjust for relocation
								magnitude += objectFile.relocationAdjustment;
							}
							//else no adjustment for absolute
							exp.defineTermOperandARC(magnitude, sign, operator, arc);
						}
						catch(NumberFormatException nfe){
							//error, expecting addr field not valid 32-bit hex string
							throw new NonFatalObjectFileFormatException(239);
						}
					}
					else{ //if C, then treat field as label
						
						//if token is a valid label
						if(token.length() <= LEN_SYMBOL_LABEL){
							for(int i = 0; i < token.length(); i++){
								
								if(!Character.isLetterOrDigit(token.charAt(i))){
									//error, addr field not valid label
									throw new NonFatalObjectFileFormatException(238);
								}
							}
							exp.defineLabelOperandARC(token.toString(), sign, operator, arc);
						}
						else{
							throw new NonFatalObjectFileFormatException(238);
						}
						
					}
				}
				//else if fields are blank, define nothing
			}
			else{
				if(isBlank){
					//error, if one field is empty, all fields must be empty
					throw new NonFatalObjectFileFormatException(236);
				}
				else{
					//error, expecting a sign "+" or "-"
					throw new NonFatalObjectFileFormatException(231);
				}
			}
		}
		else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
			//error, unexpected end of line
			throw new NonFatalObjectFileFormatException(237);
		}
		else{
			//error, expecting separator between fields
			throw new NonFatalObjectFileFormatException(234);
		}
		fieldIndex++;
		
		//if fieldIndex is within range, continue recursion
		if(fieldIndex < ((NUM_MEM_REF_TUPLES * LEN_MEM_REF_TUPLES) + NUM_PRE_MEM_REF_FIELDS - 1)){
			parseAddrExpressionTuple(objectFile, intermediateVersion, exp, tokenizer, sourceLine, token, type, isBlank, fieldIndex);
		}
		else{
			parseTextRecord(objectFile, intermediateVersion, tokenizer, sourceLine, token, type, fieldIndex);
		}
	}
	
	/**
	 * Reads in all text records in the object file.
	 * <p>
	 * Data from parsing the text records is stored in the symbolTable and intermediateVersion.
	 * 
	 * @param objectFile object file to be read from.
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param tokenizer string tokenizer.
	 * @param sourceLine line of source code.
	 * @param token string token.
	 * @param type type of token.
	 * @param fieldIndex index of field being parsed by the current recursive iteration.
	 * @throws Exception if there is a bug in the program. Normally this should not be thrown.
	 */
	private static void parseTextRecord(ObjectFile objectFile, IntermediateTable intermediateVersion, ObjectFileTokenizer tokenizer,
			StringBuffer sourceLine, StringBuffer token, ObjectFileTokenizer.TokenType[] type, int fieldIndex) throws Exception{
		
		/*
		 * Special Case: When the parser encounters fields 3, 7 and 11, they each respectively act as part of a 4-tuple
		 * along with the three fields that follow it.
		 * ie. The tuples consist of the following groups of field numbers: {(3,4,5,6), (7,8,9,10), (11,12,13,14)}
		 * 
		 * Each tuple represents a mathematical operator-term pair. Each field in a tuple represents respectively:
		 * (ARC flag, math operator flag, sign flag, address), where address may be a string symbol label or a hex 
		 * integer value. The interpretation of the address field is contingent on the ARC flag:
		 * 	- A: address is treated as a hex integer value
		 * 	- R: address is treated as a hex integer value to be adjusted for relocation
		 * 	- C: address is treated as a string symbol label, whose address will be found in the linking symbol 
		 * 	table and adjusted for relocation.
		 * 
		 * These fields (3-14) may be left blank under some conditions:
		 * 	- If one tuple is blank, then all tuples to the right of that tuple must be blank.
		 * 	- If one field of a tuple is blank, they all fields in that tuple must be blank.
		 * 		- ie. if field 3 is blank, then so are 4, 5 and 6
		*/
		switch (fieldIndex) {
		case NUM_PRE_MEM_REF_FIELDS:
			ArithmeticExpression exp;
			ArithmeticExpression.Sign sign;
			int addr = intermediateVersion.getAddr(intermediateVersion.length() - 1);
			
			if(addr >= 0){
				sign = ArithmeticExpression.Sign.POSITIVE;
			}
			else{
				sign = ArithmeticExpression.Sign.NEGATIVE;
			}
			exp = new ArithmeticExpression(Math.abs(addr), sign);
			parseAddrExpressionTuple(objectFile, intermediateVersion, exp, tokenizer, sourceLine, token, type, false, fieldIndex);
			intermediateVersion.updateOperandSymbolExpression(intermediateVersion.length() - 1, exp);
			break;
		default:
			//get separator
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
				//get next field
				tokenizer.getNextToken(sourceLine, token, type);
	
				if(type[0] == ObjectFileTokenizer.TokenType.TEXT){
					
					switch (fieldIndex) {
					case 1:
						//address
						try{
							addr = parseNumberField(token.toString(), MIN_LOAD_ADDRESS, MAX_LOAD_ADDRESS, true, OBJECT_FILE_BASE);
							//adjust for relocation
							addr += objectFile.relocationAdjustment;
							
							if(addr <= MAX_LOAD_ADDRESS){
								if(!intermediateVersion.containsLocationCounter(addr)){
									intermediateVersion.updateLocationCounter(intermediateVersion.length() - 1, addr);
								}
								else{ //if addr is already defined
									//fatal error, duplicate address.
									throw new FatalObjectFileFormatException(245);
								}
								break;
							}
							else{
								//error, address value too large. Memory overflow.
								throw new FatalObjectFileFormatException(247);
							}
							
						} catch(NumberFormatException nfe){
							//Note: bounds 0 and 4095 are hard coded into the error msg, so if MIN_LOAD_ADDRESS and MAX_LOAD_ADDRESS change,
							//then the error msg must change too
							
							//fatal error, expecting instruction code field to be a valid 32-bit hex integer in the range (0 <= addr <= 4095)
							throw new FatalObjectFileFormatException(230);
						}
					case 2:
						//instruction code
						try{
							addr = parseNumberField(token.toString(), Integer.MIN_VALUE, Integer.MAX_VALUE, true, OBJECT_FILE_BASE);
							intermediateVersion.updateInstructionCode(intermediateVersion.length() - 1, addr); //TAG - Log errors
							break;
						} catch(NumberFormatException nfe){
							//error, expecting instruction code field to be a valid 32-bit hex integer.
							throw new NonFatalObjectFileFormatException(229);
						}
					//cases 3-18 are handle by the special case described in comments above
					case (NUM_MEM_REF_TUPLES * LEN_MEM_REF_TUPLES + NUM_PRE_MEM_REF_FIELDS):
						
						//module name
						if(token.toString().compareTo(objectFile.moduleName) != 0){
							//error, text record module name doesn't match header module name
							throw new NonFatalObjectFileFormatException(227);
						}
						//else do nothing, module names match
						
						if(sourceLine.length() > 0){
							//error too many fields in text record
							throw new NonFatalObjectFileFormatException(228);
						}
						break;
					default:
						//error, this condition should never be reached.
						throw new Exception();
					}
					
					//if not last field, call recursively
					if(fieldIndex != (NUM_MEM_REF_TUPLES * LEN_MEM_REF_TUPLES + NUM_PRE_MEM_REF_FIELDS)){
						parseTextRecord(objectFile, intermediateVersion, tokenizer, sourceLine, token, type, fieldIndex + 1);
					}
					
				}
				else{ //if empty or error
					if(fieldIndex <= 1){
						//fatal error, expecting valid alphanumeric or {'-', '*', '/', ':'} characters in field
						throw new FatalObjectFileFormatException(226);
					}
					else{ //field index > 1
						//non-fatal error,  expecting valid alphanumeric or {'-', '*', '/', ':'} characters in field
						throw new NonFatalObjectFileFormatException(225);
					}
				}
			}
			else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
				
				if(fieldIndex < (NUM_MEM_REF_TUPLES * LEN_MEM_REF_TUPLES + NUM_PRE_MEM_REF_FIELDS)){
					
					if(fieldIndex <= 1){
						//fatal error, unexpected end of line
						throw new FatalObjectFileFormatException(246);
					}
					else{ //field index > 1
						//error, unexpected end of line
						throw new NonFatalObjectFileFormatException(237);
					}
				}
				//else valid end of text record, do nothing
			}
			else //if type = ERROR or TEXT
			{
				if(fieldIndex <= 1){
					//fatal error, expecting separator between fields
					throw new FatalObjectFileFormatException(224);
				}
				else{ //field index > 1
					//non-fatal error, expecting separator between fields
					throw new NonFatalObjectFileFormatException(223);
				}
			}
		}
	}
	
	/**
	 * Reads in the end records in the object file.
	 * <p>
	 * Data from parsing the end record is used to verify the contents of the
	 * rest of the object file.
	 * 
	 * @param objectFile object file to be read from.
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param tokenizer string tokenizer.
	 * @param sourceLine line of source code.
	 * @param token string token.
	 * @param type type of token.
	 * @param fieldIndex index of field being parsed by the current recursive iteration.
	 * @throws Exception if there is a bug in the program. Normally this should not be thrown.
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	private static void parseEndRecord(ObjectFile objectFile, IntermediateTable intermediateVersion, ObjectFileTokenizer tokenizer,
			StringBuffer sourceLine, StringBuffer token, ObjectFileTokenizer.TokenType[] type, int fieldIndex) throws Exception{
		//get separator
		tokenizer.getNextToken(sourceLine, token, type);
		
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			//get next field
			tokenizer.getNextToken(sourceLine, token, type);

			if(type[0] == ObjectFileTokenizer.TokenType.TEXT){
				
				switch (fieldIndex) {
				case 1:
					//total number of object file records
					int numObjectRecords = parseNumberField(token.toString(), 0, Integer.MAX_VALUE, false, OBJECT_FILE_BASE);
					
					//number of linking and text records + 1 header + 1 end
					if(numObjectRecords != (objectFile.numLinkingRecords + objectFile.numTextRecords + 2)){
						//error, invalid object file format. Total number of records fields does not mach actual total number of records.
						throw new FatalObjectFileFormatException(222);
					}
					//else, do nothing. number of records valid
					break;
				case 2:
					//module name
					if(token.toString().compareTo(objectFile.moduleName) != 0){
						//error, expecting module name field to match module name field in header record
						throw new FatalObjectFileFormatException(223);
					}
					//else, do nothing. Module names match
					break;
				default:
					//error, end record contains too many fields
					throw new FatalObjectFileFormatException(218);
				}
				parseEndRecord(objectFile, intermediateVersion, tokenizer, sourceLine, token, type, fieldIndex + 1);
				
			}
			else if(type[0] == ObjectFileTokenizer.TokenType.EMPTY){
				//error, unexpected end of line
				throw new FatalObjectFileFormatException(219);
			}
			else{ //if type == ERROR
				//error, fields may only contain alpha-numeric and {'-', '*', '/', ':'} characters 
				throw new FatalObjectFileFormatException(220);
			}
		}
		else if((type[0] == ObjectFileTokenizer.TokenType.EMPTY) && (fieldIndex == 3)){
			//valid end of end record, do nothing
		}
		else
		{
			//error, expecting separator between fields
			throw new FatalObjectFileFormatException(221);
		}
	}
	
	/**
	 * Returns the integer value of token, if token is of the designated base
	 * and within the designated bounds. Throws an exception, otherwise.
	 * 
	 * @param token string token.
	 * @param lowerBound lower bound inclusive
	 * @param upperBound upper bound inclusive
	 * @param base number base of integer represented by token.
	 * @param signed true, if token represents signed two's complements hex; false,
	 * if token represents unsigned magnitude hex.
	 * @return decimal integer representation of token.
	 * @throws NumberFormatException if token is not a valid string representation of an
	 * integer conforming to the designated bounds and base.
	 * 
	 * Change Log:<br>
	 * 	- Tobin Chee - 06/01/09<br>
	 * 		- Added a boolean parameter to consider both unsigned magnitude
	 * 		and two's complement hex integers.
	 */
	private static int parseNumberField(String token, int lowerBound, int upperBound,
			boolean signed, int base) throws NumberFormatException{
		int result;
		
		if(signed){
			result = convertTwosCompToDec(token.toString(), OBJECT_FILE_BASE, LEN_INSTRUCTION_CODE);
		}
		else{ //if unsigned
			result = Integer.parseInt(token, base);
		}
		
		if((result >= lowerBound) && (result <= upperBound)){
			return result;
		}
		else{
			//error logged by catcher
			throw new NumberFormatException();
		}
	}

	/**
	 * Reads in all records in the object files.
	 * <p>
	 * Data from parsing is stored in the symbolTable, intermediateVersion and objectFiles.
	 * 
	 * @param objectFiles array of object files.
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param symbolTable table containing symbols stored from parsing.
	 * @param tokenizer string tokenizer.
	 * @throws IOException if an I/O error occurs.
	 * @throws NullPointerException if end of file is reached unexpectedly.
	 * @throws Exception if there is a bug in the program. Normally this should not be thrown.
	 * 
	 * Change Log:<br>
	 * 	- 05/29/09 - Tobin Chee<br>
	 * 		- Changed parsing to a recursive structure to make code easily modifiable
	 * 		for changes in object file format.
	 */
	private static void parseObjectFile(ObjectFile[] objectFiles, IntermediateTable intermediateVersion,
			LinkingSymbolTable symbolTable, ObjectFileTokenizer tokenizer) throws Exception{
		
		for(int i = 0; i < objectFiles.length; i++){
			int recordCount = 0;
			StringBuffer sourceLine;
			StringBuffer token = new StringBuffer();
			//arbitrary default value. Stored as an array so it can be passed by value
			ObjectFileTokenizer.TokenType[] type = {ObjectFileTokenizer.TokenType.EMPTY};
			LinkingSymbol symbol = new LinkingSymbol();
			ObjectFile objectFile = objectFiles[i];
			
			//Header Record
			sourceLine = new StringBuffer(objectFile.inputStream.readLine());
			intermediateVersion.addEntry();
			intermediateVersion.updateSourceLine(intermediateVersion.length() - 1, sourceLine.toString());
			//BASE CASE:
			//header flag
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(token.toString().compareTo("H") == 0){
				//RECURSIVE CASE:
				parseHeaderRecord(objectFile, tokenizer, sourceLine, token, type, 1);
			}
			else{
				//fatal error, invalid header record, expecting to start with "H"
				intermediateVersion.addError(intermediateVersion.length() - 1, 205);
				throw new FatalObjectFileFormatException();
			}
			
			//Calculate relocation adjustment
			if(i > 0){
				objectFile.relocationAdjustment = objectFiles[i-1].loadAddress + objectFiles[i-1].moduleLength + objectFiles[i-1].relocationAdjustment;
			}
			//set location for header record
			intermediateVersion.updateLocationCounter(intermediateVersion.length() - 1,
					objectFile.loadAddress + objectFile.relocationAdjustment);
			
			//Linking Record
			recordCount = 0;
			
			while(recordCount < objectFile.numLinkingRecords){
				sourceLine = new StringBuffer(objectFile.inputStream.readLine());
				intermediateVersion.addEntry();
				intermediateVersion.updateSourceLine(intermediateVersion.length() - 1, sourceLine.toString());
				//clear symbol
				symbol = new LinkingSymbol();
				//BASE CASE:
				//linking flag
				tokenizer.getNextToken(sourceLine, token, type);
				
				if(token.toString().compareTo("L") == 0){
					intermediateVersion.updateLocationCounter(intermediateVersion.length() - 1,
							objectFile.loadAddress + objectFile.relocationAdjustment);
					//RECURSIVE CASE:
					parseLinkingRecord(objectFile, intermediateVersion, symbol, tokenizer, sourceLine, token, type, 1);
					
					if(recordCount == 0){
						
						//if the first symbol in the linking records matches the module name (as it should), set the proper usage
						if(symbol.getLabel().compareTo(objectFile.moduleName) == 0){
							symbol.updateUsage(LinkingSymbol.SymbolUsage.MODULE_NAME);
						}
						else{
							//fatal error, log error, first linking symbol should always match the module name
							intermediateVersion.addError(intermediateVersion.length() - 1, 203);
							throw new FatalObjectFileFormatException();
						}
					}
					
					if(symbolTable.contains(symbol.getLabel())){
						intermediateVersion.addError(intermediateVersion.length() - 1, 201);
						throw new FatalObjectFileFormatException();
					}
					//else, if valid external, do nothing b/c symbols are of type external by default.
					symbolTable.defineSymbol(symbol);
					recordCount++;
				}
				else{
					//fatal error, invalid linking record expecting record to start with "L"
					intermediateVersion.addError(intermediateVersion.length() - 1, 202);
					throw new FatalObjectFileFormatException();
				}
			}
			
			//Text Record
			recordCount = 0;
			//module length is equivalent to num text records.
			while(recordCount < objectFile.numTextRecords){
				try{
					sourceLine = new StringBuffer(objectFile.inputStream.readLine());
					intermediateVersion.addEntry();
					intermediateVersion.updateSourceLine(intermediateVersion.length() - 1, sourceLine.toString());
					intermediateVersion.setConsumesMemory(intermediateVersion.length() - 1, true);
					//BASE CASE:
					//text flag
					tokenizer.getNextToken(sourceLine, token, type);
					
					if(token.toString().compareTo("T") == 0){
						//RECURSIVE CASE:
						parseTextRecord(objectFile, intermediateVersion, tokenizer, sourceLine, token, type, 1);
					}
					else{
						throw new FatalObjectFileFormatException(200);
					}
				} catch(NonFatalObjectFileFormatException nfoffe){
					//log error and NOP instruction
					intermediateVersion.updateOpCode(intermediateVersion.length() - 1, NOP);
					intermediateVersion.addError(intermediateVersion.length() - 1, nfoffe.errorCode);
					
					//remove stack trace and debug statement
//					nfoffe.printStackTrace();
//					System.out.println(nfoffe.getMessage() + " -- " + intermediateVersion.getSourceLine(recordCount));
				} catch(FatalObjectFileFormatException foffe){
					intermediateVersion.updateOpCode(intermediateVersion.length() - 1, NOP);
					//main handles error logging
					throw foffe;
				}
				recordCount++;
			}
			
			//End Record
			sourceLine = new StringBuffer(objectFile.inputStream.readLine());
			intermediateVersion.addEntry();
			intermediateVersion.updateSourceLine(intermediateVersion.length() - 1, sourceLine.toString());
			//BASE CASE:
			//end flag
			tokenizer.getNextToken(sourceLine, token, type);
			
			if(token.toString().compareTo("E") == 0){
				intermediateVersion.updateLocationCounter(
						intermediateVersion.length() - 1,
						intermediateVersion.getLocationCounter(intermediateVersion.length() - 2) + 1);
				//RECURSIVE CASE:
				parseEndRecord(objectFile, intermediateVersion, tokenizer, sourceLine, token, type, 1);
			}
			else{
				throw new FatalObjectFileFormatException(204);
			}
		}
	}
	
	/**
	 * Writes the load file to file.
	 * 
	 * @param loadFile load file to be written to.
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param objectFiles array of object files.
	 * @throws IOException if an I/O error occurs.
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	private static void putLoadFile(BufferedWriter loadFile, IntermediateTable intermediateVersion,
			ObjectFile[] objectFiles) throws IOException{
		
			int fileLength = 0;
			
			fileLength += putHeaderRecord(loadFile, objectFiles);
			fileLength += putTextRecord(loadFile, intermediateVersion, objectFiles);
			putEndRecord(loadFile, fileLength);
	}
	
	/**
	 * Writes the header record to the load file formatted as defined in Appendix D in the CSE7000 lab manual.
	 * 
	 * @param loadFile load file to be written to.
	 * @param moduleName program name of a given source file.
	 * @param executionStartAddr address at which source program begins execution.
	 * @param moduleLength length of source program.
	 * @param initialLoadAddr initial load address of source program.
	 * @return number of records put to file.
	 * @throws IOException if an I/O error occurs.
	 */
	private static int putHeaderRecord(BufferedWriter loadFile, ObjectFile[] objectFiles) throws IOException{
		
		String temp;
		int result;
		Date todaysDate = new Date();
		ObjectFile objFirst, objLast;

		//Get first and last object files for repeated use
		objFirst = objectFiles[0];
		objLast = objectFiles[objectFiles.length - 1];
		
		//header
		temp = "LH";
		loadFile.write(temp + "|");
		
		//module name
		temp = objFirst.moduleName;
		loadFile.write(temp + "|");
		
		//execution start address
		temp = Integer.toHexString(objFirst.executionStartAddr);
		loadFile.write(temp + "|");
		
		//module length (length of entire loadfile in words)
		result = objLast.relocationAdjustment + objLast.loadAddress + objLast.moduleLength;
		//adjustment for first term, we want to ignore the leading space before the first program.
		result -= objFirst.loadAddress;
		temp = Integer.toHexString(result);
		loadFile.write(temp + "|");
		
		//load address
		temp = Integer.toHexString(objFirst.loadAddress);
		loadFile.write(temp + "|");
		
		//date|time - yyyy:ddd|time hh:mm:ss
		SimpleDateFormat dateNow = new SimpleDateFormat("yyyy:DDD|HH:mm:ss");
		temp = dateNow.format(todaysDate);
		loadFile.write(temp + "|");
		
		//"CSE7000-LINK"
		temp = "CSE7000-LINK";
		loadFile.write(temp + "|");
		
		//Version# (going to assume this is the assembler version)
		temp = "VERSION " + VERSION_NUMBER;
		loadFile.write(temp + "|");
		
		//Revision# (going to assume this is the assembler version)
		temp = "REVISION " + REVISION_NUMBER;
		loadFile.write(temp + "\n");
		
		return 1; //length is always one
	}
	
	/**
	 * Writes the text record to the load file formatted as defined in Appendix D in the CSE7000 lab manual.
	 * 
	 * @param loadFile load file to be written to.
	 * @param intermediateVersion table containing instructions codes and memory addresses from parsing.
	 * @param objectFiles array of object files.
	 * @return number of records put to file.
	 * @throws IOException if an I/O error occurs.
	 * 
	 * Change Log:<br>
	 * 	- Tobin Chee - 06/02/09
	 * 		- While loop with a for loop to fix counting accuracy.
	 */
	private static int putTextRecord(BufferedWriter loadFile, IntermediateTable intermediateVersion, ObjectFile[] objectFiles) throws IOException{

		int recordCount = 0;
		String temp;
		
		for(int i = 0; i < intermediateVersion.length(); i++){
			
			if(intermediateVersion.getConsumesMemory(i)){
				//header
				temp = "LT";
				loadFile.write(temp + "|");
				
				//load address per instruction
				temp = Integer.toHexString(intermediateVersion.getLocationCounter(i));
				
				while(temp.length() < LEN_UNSIGNED_NUM_FIELDS){
					temp = "0" + temp;
				}
				loadFile.write(temp + "|");
				
				//instruction code
				temp = Integer.toHexString(intermediateVersion.getInstructionCode(i));
				
				while(temp.length() < LEN_INSTRUCTION_CODE){
					temp = "0" + temp;
				}
				loadFile.write(temp + "|");
				
				//module name
				temp = objectFiles[0].moduleName;
				loadFile.write(temp + "\n");

				recordCount++;
			}
			//else do nothing, only instructions go in the loadfile
		}
		return recordCount;
	}
	
	/**
	 * Writes the end record to the load file formatted as defined in Appendix D in the CSE7000 lab manual.
	 * 
	 * @param loadFile load file to be written to.
	 * @param fileLength number of lines in the load file prior to this method's execution (preserves).
	 * @throws IOException if an I/O error occurs. 
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	private static void putEndRecord(BufferedWriter loadFile, int fileLength) throws IOException{
		loadFile.write("LE|" + Integer.toHexString(fileLength + 1));
	}
	
	/**
	 * Validates the command line arguments passed into LinkerLoader.
	 * <p>
	 * Checks to ensure the file names are of the form "obectFile.o" and "loadFile.xe".
	 * Checks to ensure the object file exists. outputs the appropriate errors to the
	 * console if invalid file names are given. Does not check for file names containing
	 * invalid Windows file characters, such as {"\", "*", "&", ...}. That error is caught
	 * by the try catch block in main.
	 * 
	 * @param args command line arguments.
	 * @return true, if args are valid; false, if args are invalid.
	 * 
	 * Change Log:<br>
	 * 	- 05/28/09 - Tobin Chee<br>
	 * 		- Updated implementation to permit multiple object file arguments.<br>
	 * 		- Updated error messages<br><br>
	 * 	- 06/02/09 - Tobin Chee<br>
	 * 		- Changed load file extension from ".lf" to ".xe".
	 */
	private static boolean validateArgs(String[] args){
		boolean result = true;
		int count = 0;
		
		if(args.length >= MIN_NUM_OF_ARGS){
			
			if(args.length > MAX_NUM_OF_ARGS){
				System.out.println("WARNING: Invalid arguments. Number of arguments has exceeded the maximum of " + MAX_NUM_OF_ARGS + ". Additional arguments have been be ignored.");
			}
			
			if(args[count].length() > 3){
				
				//if load file argument ends in ".xe"
				if((args[0].charAt(args[0].length() - 3) == '.') &&
						(args[0].charAt(args[0].length() - 2) == 'x') &&
						(args[0].charAt(args[0].length() - 1) == 'e')){
					
					count++;
					//if loadFile arg is valid, validate objectFile args. If there are more than 5 object files (6 args total) ignore the extra ones.
					while((count < MAX_NUM_OF_ARGS) && (count < args.length) && result){
						
						if(args[count].length() > MIN_NUM_OF_ARGS){
							
							if((args[count].charAt(args[count].length() - 2) == '.')
								&& (args[count].charAt(args[count].length() - 1) == 'o')){
								File f;
								f = new File(args[count]);
								
								if(f.exists()){
									//do nothing, argument is valid
								}
								else{
									result = false;
									System.out.println("FATAL ERROR: Invalid object file name (argument " + count + "). File does not exist.");
								}
								count++;
							}
							else{
								result = false;
								System.out.println("FATAL ERROR: Invalid object file name (argument " + count + "). Object file name must end in \".o\"");
							}
						}
						else{
							result = false;
							System.out.println("FATAL ERROR: Invalid object file name (argument " + count + "). Object file name must be atleast 3 characters long and end in \".o\"");
						}
					}
				}
				else{
					result = false;
					System.out.println("FATAL ERROR: Invalid load file name (argument " + count + "). Load file name must end in \".xe\"");
				}
			}
			else{
				result = false;
				System.out.println("FATAL ERROR: Invalid load file name (argument " + count + "). Load file name must be atleast 4 characters long and end in \".xe\"");
			}
		}
		else{
			result = false;
			System.out.println("FATAL ERROR: Invalid arguments. Program required atleast " + MIN_NUM_OF_ARGS + " command line arguments (loadfile.xe objectFile.o)");
		}	
		return result;
	}

}
